{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "parameters"
    ]
   },
   "outputs": [],
   "source": [
    "epochs = 10\n",
    "n_test_batches = 200"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Parte 11 - Classificação Segura Usando Aprendizagem Profunda\n",
    "\n",
    "\n",
    "## Os seus dados são importantes, o seu modelo também.\n",
    "\n",
    "Os dados são o carro-chefe por trás da Aprendizagem de Máquina (Machine Learning). As organizações que criam e coletam dados são capazes de construir e treinar seus próprios modelos de aprendizagem de máquina. Isto permite-lhes oferecer o uso de tais modelos como um serviço (MLaaS) a organizações externas. Isto é útil para organizações que podem não ser capazes de criar esses modelos por si próprias, mas que ainda gostariam de usar esses modelos para fazer predições sobre os seus próprios dados.\n",
    "\n",
    "No entanto, um modelo hospedado na nuvem ainda apresenta um problema de privacidade/IP. Para que organizações externas possam usá-lo - elas devem carregar seus dados de entrada (como imagens a serem classificadas) ou fazer o _download_ do modelo. O _upload_ dos dados de entrada pode ser problemático do ponto de vista da privacidade, mas o _download_ do modelo pode não ser uma opção se a organização que criou/possui o modelo estiver preocupada em preservar seu IP.\n",
    "\n",
    "\n",
    "\n",
    "## Computação sobre dados criptografados\n",
    "\n",
    "Neste contexto, uma possível solução seria criptografar tanto o modelo como os dados de uma forma que permita a uma organização utilizar um modelo de outra organização sem revelar o seu IP. Existem várias estratégias de criptografia que permitem o cálculo sobre dados criptografados, entre os quais _Secure Multi-Party Computation_ (SMPC), criptografia Homomórfica (FHE/SHE) e criptografia Funcional (FE) são os tipos mais conhecidos. Vamos focar aqui na Computação Multiparte Segura ([introduzida em detalhe aqui no tutorial 5](https://github.com/OpenMined/PySyft/blob/master/examples/tutorials/translations/portugu%C3%AAs/Parte%2005%20-%20Bem-vindo%20ao%20Sandbox.ipynb)) que consiste na soma de partes compartilhadas de forma privada. E que se baseia em protocolos de criptografia como o SecureNN e SPDZ, cujos detalhes são dados [nesta excelente publicação de de blog](https://mortendahl.github.io/2017/09/19/private-image-analysis-with-mpc/). \n",
    "\n",
    "Estes protocolos alcançam desempenhos notáveis sobre dados criptografados, e nos últimos meses temos trabalhado para tornar estes protocolos fáceis de usar. Em específico, estamos construindo ferramentas para permitir que você use esses protocolos sem ter que reimplementá-los novamente (ou se quer conhecer a criptografia por trás de seu funcionamento). Vamos direto ao assunto.\n",
    "\n",
    "## Cenário\n",
    "\n",
    "Neste tutorial considere o seguinte cenário: considere que você é o servidor e que você tem alguns dados. Primeiro, você define e treina um modelo com esses dados de treinamento privado. Depois, você entra em contato com um cliente que possui alguns dos seus próprios dados e que gostaria de acessar o seu modelo para fazer algumas previsões/predições. \n",
    "\n",
    "Você criptografa o seu modelo (uma rede neural). O cliente criptografa os seus dados. Ambos usam esses dois recursos criptografados para usar o modelo para classificar os dados. Finalmente, o resultado da predição é enviado de volta para o cliente de forma criptografada para que o servidor (ou seja, você) não aprenda nada sobre os dados do cliente (ou seja, você não aprende nem as entradas nem as predições).\n",
    "\n",
    "O ideal seria que, de maneira incremental, compartilhássemos as `entradas dos clientes` entre eles próprios e o `servidor` e vice-versa para o modelo. Por uma questão de simplicidade, as entradas (i.e dados de treino) serão mantidas por outros dois _workers_ , `alice` e `bob`. Se você considerar que alice é de propriedade do cliente e bob do servidor, é completamente equivalente.\n",
    "\n",
    "O cálculo é seguro no modelo HBC (i.e. Honest-But-Curious adversary), que é padrão em [muitas ferramentas MPC](https://arxiv.org/pdf/1801.03239.pdf) (Multi-Party Computation).\n",
    "\n",
    "**Agora temos tudo o que precisamos!**\n",
    "\n",
    "\n",
    "Autor:\n",
    "- Théo Ryffel - Twitter: [@theoryffel](https://twitter.com/theoryffel) · GitHub: [@LaRiffle](https://github.com/LaRiffle)\n",
    "\n",
    "Tradução:\n",
    "- Jeferson Silva - Github: [@jefersonf](https://github.com/jefersonf)\n",
    "\n",
    "**Vamos começar!**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Imports e especificações do modelo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "from torchvision import datasets, transforms"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "  Nós também precisamos executar comandos específicos para importar/iniciar o PySyft. Nós criamos alguns _workers_ (chamados `client`, `bob` e `alice`). Por fim, nós definimos o provedor de criptografia (`cripto_provider`) que nos dá todos as ferramentas básicas de criptografia que podemos precisar ([Veja nosso tutorial sobre SMPC para mais detalhes](https://github.com/OpenMined/PySyft/blob/master/examples/tutorials/Part%2009%20-%20Intro%20to%20Encrypted%20Programs.ipynb))."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import syft as sy\n",
    "hook = sy.TorchHook(torch) \n",
    "client = sy.VirtualWorker(hook, id=\"client\")\n",
    "bob = sy.VirtualWorker(hook, id=\"bob\")\n",
    "alice = sy.VirtualWorker(hook, id=\"alice\")\n",
    "crypto_provider = sy.VirtualWorker(hook, id=\"crypto_provider\") "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nós definimos o cenário da tarefa de aprendizagem"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Arguments():\n",
    "    def __init__(self):\n",
    "        self.batch_size = 64\n",
    "        self.test_batch_size = 50\n",
    "        self.epochs = epochs\n",
    "        self.lr = 0.001\n",
    "        self.log_interval = 100\n",
    "\n",
    "args = Arguments()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Carregamento e envio de dados aos Workers\n",
    "\n",
    "Na nosso cenário, assumimos que o servidor tem acesso a alguns dados de treino para que possa treinar seu modelo. Aqui temos o conjunto de treinamento do MNIST."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_loader = torch.utils.data.DataLoader(\n",
    "    datasets.MNIST('../data', train=True, download=True,\n",
    "                   transform=transforms.Compose([\n",
    "                       transforms.ToTensor(),\n",
    "                       transforms.Normalize((0.1307,), (0.3081,))\n",
    "                   ])),\n",
    "    batch_size=args.batch_size, shuffle=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Em segundo lugar, o cliente tem alguns dados e gostaria de ter predições sobre eles usando o modelo do servidor. Este cliente criptografa seus dados, compartilhando-os entre os dois _workers_ , `alice` e `bob`.\n",
    "> SMPC usa protocolos de criptografia que requerem o uso de inteiros. Nós aproveitamos aqui a abstração do tensor PySyft para converter os tensores de ponto flutuante do PyTorch em tensores de precisão fixa utilizando `.fix_precision()`. Por exemplo 0.123 com precisão 2 faz um arredondamento com o segundo dígito decimal para que o número armazenado seja o número inteiro 12.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_loader = torch.utils.data.DataLoader(\n",
    "    datasets.MNIST('../data', train=False,\n",
    "                   transform=transforms.Compose([\n",
    "                       transforms.ToTensor(),\n",
    "                       transforms.Normalize((0.1307,), (0.3081,))\n",
    "                   ])),\n",
    "    batch_size=args.test_batch_size, shuffle=True)\n",
    "\n",
    "private_test_loader = []\n",
    "for data, target in test_loader:\n",
    "    private_test_loader.append((\n",
    "        data.fix_precision().share(alice, bob, crypto_provider=crypto_provider),\n",
    "        target.fix_precision().share(alice, bob, crypto_provider=crypto_provider)\n",
    "    ))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Especificação de uma Rede Neural (Feed Forward Neural Network)\n",
    "\n",
    "Aqui está a especificação da rede utilizada pelo servidor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Net(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(Net, self).__init__()\n",
    "        self.fc1 = nn.Linear(784, 500)\n",
    "        self.fc2 = nn.Linear(500, 10)\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = x.view(-1, 784)\n",
    "        x = self.fc1(x)\n",
    "        x = F.relu(x)\n",
    "        x = self.fc2(x)\n",
    "        return x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Iniciar o treinamento\n",
    "O treinamento é feito localmente, então este é um treinamento no PyTorch inteiramente local, nada de especial aqui!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train(args, model, train_loader, optimizer, epoch):\n",
    "    model.train()\n",
    "    for batch_idx, (data, target) in enumerate(train_loader):\n",
    "        optimizer.zero_grad()\n",
    "        output = model(data)\n",
    "        output = F.log_softmax(output, dim=1)\n",
    "        loss = F.nll_loss(output, target)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        if batch_idx % args.log_interval == 0:\n",
    "            print('Train Epoch: {} [{}/{} ({:.0f}%)]\\tLoss: {:.6f}'.format(\n",
    "                epoch, batch_idx * args.batch_size, len(train_loader) * args.batch_size,\n",
    "                100. * batch_idx / len(train_loader), loss.item()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = Net()\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=args.lr)\n",
    "\n",
    "for epoch in range(1, args.epochs + 1):\n",
    "    train(args, model, train_loader, optimizer, epoch)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def test(args, model, test_loader):\n",
    "    model.eval()\n",
    "    test_loss = 0\n",
    "    correct = 0\n",
    "    with torch.no_grad():\n",
    "        for data, target in test_loader:\n",
    "            output = model(data)\n",
    "            output = F.log_softmax(output, dim=1)\n",
    "            test_loss += F.nll_loss(output, target, reduction='sum').item() # sum up batch loss\n",
    "            pred = output.argmax(1, keepdim=True) # get the index of the max log-probability \n",
    "            correct += pred.eq(target.view_as(pred)).sum().item()\n",
    "\n",
    "    test_loss /= len(test_loader.dataset)\n",
    "\n",
    "    print('\\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\\n'.format(\n",
    "        test_loss, correct, len(test_loader.dataset),\n",
    "        100. * correct / len(test_loader.dataset)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test(args, model, test_loader)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "O nosso modelo está agora treinado e pronto para ser fornecido como um serviço!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Avaliação segura"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Agora, como servidor, enviamos o modelo para os _workers_ que têm os dados. Como o modelo é informação sensível (você gastou tempo otimizando-o!), você não quer revelar seus pesos então você compartilha o modelo, de forma secreta/criptografada, como nós fizemos com o conjunto de dados anteriormente."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.fix_precision().share(alice, bob, crypto_provider=crypto_provider)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A seguinte função de teste realiza uma avaliação criptografada. Os pesos do modelo, as entradas de dados, a predição e o alvo (target) utilizado para a pontuação são criptografados!\n",
    "\n",
    "No entanto, a sintaxe é muito semelhante ao teste de modelos usual do PyTorch, não é legal/Fixe?!\n",
    "\n",
    "A única coisa que deciframos/descriptografamos no lado do servidor é a pontuação final dos nossos 200 batches de itens (`n_test_batches`) para verificar se as previsões eram em média boas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def test(args, model, test_loader):\n",
    "    model.eval()\n",
    "    n_correct_priv = 0\n",
    "    n_total = 0\n",
    "    with torch.no_grad():\n",
    "        for data, target in test_loader[:n_test_batches]:\n",
    "            output = model(data)\n",
    "            pred = output.argmax(dim=1) \n",
    "            n_correct_priv += pred.eq(target.view_as(pred)).sum()\n",
    "            n_total += args.test_batch_size\n",
    "\n",
    "            n_correct = n_correct_priv.copy().get().float_precision().long().item()\n",
    "    \n",
    "            print('Test set: Accuracy: {}/{} ({:.0f}%)'.format(\n",
    "                n_correct, n_total,\n",
    "                100. * n_correct / n_total))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test(args, model, private_test_loader)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Et voilà! Aqui está, você aprendeu como fazer previsões/predições seguras de ponta a ponta: os pesos do modelo do servidor não vazaram para o cliente e o servidor não tem informações sobre os dados de entrada nem sobre a saída da classificação!\n",
    "\n",
    "Quanto ao desempenho, classificar uma imagem leva **menos de 0,1 segundo**, aproximadamente **33ms** no meu computador portátil (2,7 GHz Intel Core i7, 16GB RAM). Entretanto, utilizei uma comunicação muito rápida (todos os _workers_ estão na minha máquina local). O desempenho irá variar dependendo da rapidez com que os diferentes _workers_ podem se comunicar uns com os outros.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conclusão\n",
    "\n",
    "Você já viu como é fácil usar o PyTorch e o PySyft para realizar Aprendizagem de Máquina segura (i.e Secure Machine Learning) na prática e proteger os dados dos usuários, sem ter que ser um especialista em criptografia!\n",
    "\n",
    "Mais informações sobre este tópico virão em breve, incluindo camadas convolucionais para avaliar adequadamente o desempenho do PySyft em relação a outros casos de uso, bem como o treinamento criptografado privado de redes neurais, que é necessário quando uma organização recorre a dados sensíveis externos para treinar seu próprio modelo. Fique atento!\n",
    "\n",
    "Se você gostou disso e gostaria de participar do movimento em prol da preservação da privacidade, da propriedade descentralizada da IA e da cadeia de fornecimento de IA (dados), você pode fazer isso das seguintes maneiras! \n",
    "\n",
    "\n",
    "### Dê-nos uma estrela em nosso repo do PySyft no GitHub\n",
    "\n",
    "A maneira mais fácil de ajudar nossa comunidade é adicionando uma estrela nos nossos repositórios! Isso ajuda a aumentar a conscientização sobre essas ferramentas legais que estamos construindo.\n",
    "\n",
    "- [Star PySyft](https://github.com/OpenMined/PySyft)\n",
    "\n",
    "### Veja nossos tutoriais no GitHub!\n",
    "\n",
    "Fizemos tutoriais muito bons para entender melhor como deve ser a Aprendizagem Federada e a proteção de Privacidade, e como estamos construindo as coisas básicas que precisamos para fazer com que isso aconteça.\n",
    "\n",
    "- [Tutoriais do PySyft](https://github.com/OpenMined/PySyft/tree/master/examples/tutorials)\n",
    "\n",
    "### Junte-se ao Slack!\n",
    "\n",
    "A melhor maneira de manter-se atualizado sobre os últimos avanços é se juntar à nossa comunidade! \n",
    "\n",
    "- [http://slack.openmined.org](http://slack.openmined.org)\n",
    "\n",
    "### Contribua com o projeto!\n",
    "\n",
    "A melhor maneira de contribuir para a nossa comunidade é se tornando um contribuidor do código! A qualquer momento, você pode acessar a página de *Issues* (problemas) do PySyft no GitHub e filtrar por \"Projetos\". Isso mostrará todas as etiquetas (tags) na parte superior, com uma visão geral de quais projetos você pode participar! Se você não deseja ingressar em um projeto, mas gostaria de codificar um pouco, também pode procurar mais mini-projetos \"independentes\" pesquisando problemas no GitHub marcados como \"good first issue\".\n",
    "\n",
    "- [Etiquetados como Good First Issue](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n",
    "\n",
    "### Doar\n",
    "\n",
    "Se você não tem tempo para contribuir com nossa base de códigos, mas ainda deseja nos apoiar, também pode se tornar um Apoiador em nosso Open Collective. Todas as doações vão para hospedagem na web e outras despesas da comunidade, como hackathons e meetups!\n",
    "\n",
    "[Página do Open Collective do OpenMined](https://opencollective.com/openmined)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
